HLS(http live streaming) 的移动端播放
这是一个移动端的直播项目,还有在线互动功能。
首先视频我们想到的都是使用video标签,但是ios虽然可以使用,但是安卓不行,所以在这里,我们需要对移动端进行判断,ios/andriod分别使用不同的组件。
先来对移动端进行判断
(def ios? (->> (.-userAgent js/navigator)
(re-seq #"iPhone")
(count)
zero?
not))
这里我们只需要对ios进行判断就可以了,浏览器的用户代理(.-userAgent js/navigator)。
ios/andriod判断成功,那我们看看ios和andriod都用什么组件实现视频功能。
(if ios?
[:video
{:width "100%"
:poster "/imgs/zhanweitu.jpg"
:controls "controls"
:src "http://xhlive.3vyd.com/live/007.m3u8"}]
[:> Player {:playsInline true
:poster "/imgs/zhanweitu.jpg"
:src "http://xhlive.3vyd.com/live/007.m3u8"}
])
上面说了,ios可以直接使用video,这里安卓用的player(["video-react" :refer [Player]]这是组件在clojure中的调用方法)。不过这个组件同样在ios上不能进行视频播放。
有兴趣的小伙伴可以看看video.js和video-react,很有趣。
这里遇到过一个坑,在if外面套了两层的box(["@material-ui/core/Box" :default Box],项目结构使用的material-ui框架),播放的视频要是比较大,会把下层的兄弟给覆盖掉,限制高度也不行,还是会被覆盖掉,所以写的时候还是要注意,尽量不要留多余的代码,保持良好的习惯,这种bug是绝对不应该有的!!!(反思)
下面的互动聊天,用到了MessageList(["react-chat-elements" :refer [MessageBox MessageList]]),显示聊天记录。
(defn msg->data [msgs]
(mapv (fn [value]
{:position "right"
:type "text"
:text (:msg value)
:date (:time value)})
msgs))
[:> MessageList
{:className "message-list"
:lockable true
:toBottomHeight "100%"
:dataSource (msg->data @(rf/subscribe [:all-msg]))}]
聊天内容和发送的内容都是使用的websocket,关于这个以前有一篇专门的博客。
还有MessageList的使用,刚开始写的时候没有限定高度,所以滚动条是在一整个页面滚动的,这样发送数据的时候,发现最新的一条没有展示出来。还需要手动滚动才行,这样的体验很不好。所以在这里height要写出来,bug就自然解决了。
在这里再展示一下大牛的写法:
(def theme (createMuiTheme #js {:palette}) )
(def useStyles
(makeStyles
(fn [theme]
(clj->js {:root {:padding "2px 4px",:display "flex",:alignItems "center",:width 400,},
:input {:marginLeft (.spacing theme 1) :flex 1,},
:iconButton {:padding 10,},
:divider {:height 28,:margin 4,}}))))
(let [classes (useStyles) ]
[:> Box {:align-self :flex-end
:width "100%"}
[:> Paper {:component "form"
:id "box-input"
:class-name (.-root classes)}
[:> InputBase {:placeholder "请输入您的发言"
:id "messge-input"
:on-change (fn [event ]
(reset! current-msg (.. event -target -value) ))
:class-name (.-input classes)}]
[:> Divider {:orientation "vertical"
:class-name (.-divider classes)}]
[:> Button {:color "primary"
:aria-label "directions"
:onClick (fn []
(prn "发出新消息...")
(when-not (empty? @current-msg)
(client/chsk-send! [:user/new-msg @current-msg])
(set! (.-value (.getElementById js/document "messge-input")) "")
(reset! current-msg "")))}
"提交"
[:> DirectionsIcon]]]])
这是发送内容的部分
下面是websocket的数据传输及数据存储
(timbre/set-level! :debug)
(defn ->output! [fmt & args]
(let [msg (apply encore/format fmt args)]
(timbre/info msg)))
(let [ ;; For this example, select a random protocol:
rand-chsk-type :ws
_ (->output! "Randomly selected chsk type: %s" rand-chsk-type)
;; Serializtion format, must use same val for client + server:
packer :edn ; Default packer, a good choice in most cases
;; (sente-transit/get-transit-packer) ; Needs Transit dep
{:keys [chsk ch-recv send-fn state]}
(sente/make-channel-socket-client!
"/chsk" ; Must match server Ring routing URL
""
{:protocol :https
:host "xhliveactivity.3vyd.com"
:port 443
:type rand-chsk-type
:packer packer})]
(def chsk chsk)
(def ch-chsk ch-recv) ; ChannelSocket's receive channel
(def chsk-send! send-fn) ; ChannelSocket's send API fn
(def chsk-state state) ; Watchable, read-only atom
)
(defmulti -event-msg-handler "Multimethod to handle Sente `event-msg`s"
:id ; Dispatch on event-id
)
(defn event-msg-handler
"Wraps `-event-msg-handler` with logging, error catching, etc."
[{:as ev-msg :keys [id ?data event]}]
(-event-msg-handler ev-msg))
(defmethod -event-msg-handler
:default ; Default/fallback case (no other matching handler)
[{:as ev-msg :keys [event]}]
(->output! "Unhandled event: %s" event))
(kf/reg-event-db
:init-msg
(fn [db [event]]
(prn "init......db with msg" event)
(assoc db :msgs event)))
(kf/reg-event-db
:init-users
(fn [db [event]]
(prn "init......db with users")
(assoc db :users event)))
(rf/reg-sub
:all-msg
(fn [db]
(:msgs db)))
(rf/reg-event-db
:add-msg
(fn [db [_ event]]
(prn "event:" event)
(assoc db :msgs
(conj (:msgs db )
event))))
(rf/reg-event-db
:modify-users
(fn [db [_ count]]
(prn "event:modify-users" count)
(assoc db :users count)))
(defn give-me-all []
(chsk-send! [:user/get-all-msgs nil]
1000
(fn [cb-reply]
(when (cb-success? cb-reply)
(if cb-reply
(do
(rf/dispatch [:init-msg cb-reply])
(prn "success:" cb-reply))
(do
(prn "failed:" cb-reply)
))))))
(defn give-init-users []
(chsk-send! [:user/get-users nil]
1000
(fn [cb-reply]
(when (cb-success? cb-reply)
(if cb-reply
(do
(rf/dispatch [:init-users cb-reply])
(prn "success:" cb-reply))
(prn "failed:" cb-reply)
)))))
(defmethod -event-msg-handler :chsk/state
[{:as ev-msg :keys [?data]}]
(let [[old-state-map new-state-map] (have vector? ?data)]
(if (:first-open? new-state-map)
(do
(prn "........................")
(->output! "Channel socket successfully established!: %s" new-state-map)
(give-me-all)
(give-init-users))
(->output! "Channel socket state change: %s" new-state-map))))
(defmethod -event-msg-handler :chsk/recv
[{:as ev-msg :keys [?data]}]
(->output! "Push event from server: %s" ?data)
(let [[event-id event-body] ?data]
(prn "=======" event-id)
(case event-id
:msg/broad-cast (rf/dispatch [:add-msg event-body])
:msg/modify-users (rf/dispatch [:modify-users event-body]))))
(defmethod -event-msg-handler :chsk/handshake
[{:as ev-msg :keys [?data]}]
(let [[?uid ?csrf-token ?handshake-data] ?data]
(->output! "Handshake: %s" ?data)))
;; :msg/broad-cast
(defmethod -event-msg-handler :msg/broad-cast
[{:as ev-msg :keys [?data]}]
(let [[?uid ?csrf-token ?handshake-data] ?data]
(rf/dispatch [:add-msg ?data])))
(defonce router_ (atom nil))
(defn stop-router! [] (when-let [stop-f @router_] (stop-f)))
(defn start-router! []
(stop-router!)
(reset! router_
(sente/start-client-chsk-router!
ch-chsk event-msg-handler)))
(defn start! [] (start-router!))
(defonce _start-once (start!))
(comment
(a/go-loop []
(println (a/<! ch-chsk))
(recur))
)
(rf/reg-fx :ws-event
(fn [[msg-id event]]
(chsk-send! [msg-id event])))
(rf/reg-fx :ws-event-cb
(fn [[msg-id event success-id fail-id :as m] ]
(prn "m:" m)
(chsk-send! [msg-id event] 1000
(fn [cb-reply]
(when (cb-success? cb-reply)
(if cb-reply
(do
(rf/dispatch [success-id cb-reply])
(->output! "消息 %s 成功, 返回值:%s" m cb-reply))
(do
(->output! "消息 %s 失败" m)
(rf/dispatch [fail-id cb-reply]))))))))